﻿using Engine.Graphics;
using Engine.Graphics.VertexStructures;
using SharpDX;
using SharpDX.Direct3D11;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Engine.FileFormats3D
{
    public class TreeFile : IRenderableObject
    {
        #region Private Fields

        public VertexBufferBinding vertexBufferBinding;
        public VertexBufferBinding vertexBufferBindingLeaves;
        private Engine engine;
        private Header header;
        private SharpDX.Direct3D11.Buffer ib;
        private BufferDescription indexBufferDescription;
        private short[] indices;
        private SharpDX.Direct3D11.Buffer instanceLeavesBuffer;
        private BufferDescription instanceLeavesBufferDescription;
        private LeavePosition[] leavePositions;

        /// <summary>
        /// TextureId where the textures are stored in TextureManager.
        /// </summary>
        private int[] textureIds;

        private TextureManager textureManager;
        private string textureNameLeaves;
        private string textureNameTrunk;
        private SharpDX.Direct3D11.Buffer vb;
        private BufferDescription vertexBufferDescription;
        private MyOwnVertex.MyPositionNormalTextured[] vertices;

        #endregion Private Fields

        #region Public Constructors

        public TreeFile(Engine engine)
        {
            this.engine = engine;

            this.vertexBufferDescription = new BufferDescription()
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = 0,
                SizeInBytes = 0,
                Usage = ResourceUsage.Default
            };
            this.instanceLeavesBufferDescription = new BufferDescription()
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = 0,
                SizeInBytes = 0,
                Usage = ResourceUsage.Default
            };
            this.indexBufferDescription = new BufferDescription()
            {
                BindFlags = BindFlags.IndexBuffer,
                CpuAccessFlags = 0,
                SizeInBytes = 0,
                Usage = ResourceUsage.Default,
                OptionFlags = ResourceOptionFlags.None
            };
        }

        #endregion Public Constructors

        #region Public Properties

        public SharpDX.Direct3D11.Buffer IndexBuffer
        {
            get { return ib; }
            set { ib = value; }
        }

        /// <summary>
        /// For each leave the position and color are stored in this buffer. For shader instanceing rendering.
        /// </summary>
        public SharpDX.Direct3D11.Buffer InstanceLeavesBuffer
        {
            get { return instanceLeavesBuffer; }
            set { instanceLeavesBuffer = value; }
        }

        public LeavePosition[] LeavePositions
        {
            get { return leavePositions; }
            set { leavePositions = value; }
        }

        public int[] TextureIds
        {
            get { return textureIds; }
            set { textureIds = value; }
        }

        public SharpDX.Direct3D11.Buffer VertexBuffer
        {
            get { return vb; }
            set { vb = value; }
        }

        public MyOwnVertex.MyPositionNormalTextured[] Vertices
        {
            get
            {
                return this.vertices;
            }
        }

        #endregion Public Properties

        #region Public Methods

        public static TreeFile FromFile(Engine engine, string filename, TextureManager textureManager)
        {
            TreeFile treeFile;
            FileStream fs = new FileStream(filename, FileMode.Open);
            BinaryReader br = new BinaryReader(fs);

            treeFile = FromStream(engine, br, textureManager);

            // Add the textures to the textures manger.
            treeFile.textureIds = new int[2];

            string textureFolder = treeFile.textureManager.TextureFolder;
            if (!File.Exists(treeFile.textureManager.TextureFolder + treeFile.textureNameLeaves))
                treeFile.textureManager.TextureFolder = Path.GetDirectoryName(filename) + "\\Textures\\";
            treeFile.textureIds[1] = treeFile.textureManager.AddTexture(treeFile.textureNameLeaves);
            treeFile.textureManager.TextureFolder = textureFolder;

            if (!File.Exists(treeFile.textureManager.TextureFolder + treeFile.textureNameTrunk))
                treeFile.textureManager.TextureFolder = Path.GetDirectoryName(filename) + "\\Textures\\";
            treeFile.textureIds[0] = treeFile.textureManager.AddTexture(treeFile.textureNameTrunk);
            treeFile.textureManager.TextureFolder = textureFolder;

            br.Close();
            fs.Close();
            return treeFile;
        }

        public void Dispose()
        {
            if (this.vb != null)
                this.vb.Dispose();
            this.vb = null;
            if (this.ib != null)
                this.ib.Dispose();
            this.ib = null;
            if (this.instanceLeavesBuffer != null)
                this.instanceLeavesBuffer.Dispose();
            this.instanceLeavesBuffer = null;
        }

        public int GetPriorityLevel()
        {
            return 50;
        }

        public void Render(int renderStep)
        {
            lock (this.engine.Device.ImmediateContext)
            {
                this.engine.Device.ImmediateContext.InputAssembler.SetVertexBuffers(0, this.vertexBufferBinding);
                this.engine.Device.ImmediateContext.InputAssembler.SetIndexBuffer(this.ib, SharpDX.DXGI.Format.R16_UInt, 0);
                this.engine.Device.ImmediateContext.DrawIndexed(this.indices.Length, 0, 0);
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal void RenderLeaves(int numberOfInstances)
        {
            //   this.engine.Device.ImmediateContext.DrawIndexed(this.vertices.Length, 0, 2);

            //this.engine.Device.ImmediateContext.DrawIndexedInstanced(this.leavePositions.Length * 6, numberOfInstances, 0, 0, 0);
            this.engine.Device.ImmediateContext.DrawIndexedInstanced(6, numberOfInstances, 0, 0, 0);
        }

        // Internal Methods (2) 
        internal void RenderTrunks()
        {
            // Check if the tree have a trunk
            if (this.indices.Length / 3 - this.leavePositions.Length * 2 <= 0)
                return;
            // Trunk starts after leaves
            //this.engine.Device.ImmediateContext.DrawIndexed(this.indices.Length, this.indices.Length / 3 - this.leavePositions.Length * 2, this.leavePositions.Length * 6);
            this.engine.Device.ImmediateContext.DrawIndexed(this.indices.Length - this.leavePositions.Length * 6, this.leavePositions.Length * 6, 0);
        }

        internal void RenderTrunksInstanced(int instanceCount)
        {
            // Check if the tree have a trunk
            if (this.indices.Length / 3 - this.leavePositions.Length * 2 <= 0)
                return;
            // Trunk starts after leaves
            this.engine.Device.ImmediateContext.DrawIndexedInstanced(this.indices.Length - this.leavePositions.Length * 6, instanceCount, this.leavePositions.Length * 6, 0, 0);
        }

        #endregion Internal Methods

        #region Private Methods

        private static TreeFile FromStream(Engine engine, BinaryReader br, TextureManager textureManager)
        {
            br.BaseStream.Position = 0;
            TreeFile treeFile = new TreeFile(engine);
            treeFile.header = Header.FromStream(br);
            treeFile.textureManager = textureManager;

            // updating the texture name. Remove the "\0" from stream and changed extension to ".ptx".
            treeFile.textureNameTrunk = new string(br.ReadChars(64)).Replace("\0", "");
            string extension = Path.GetExtension(treeFile.textureNameTrunk).ToUpper();
            if (extension == ".TGA")
                treeFile.textureNameTrunk = treeFile.textureNameTrunk.Substring(0, treeFile.textureNameTrunk.Length - 3) + "ptx";

            // updating the texture name. Remove the "\0" from stream and changed extension to ".ptx".
            treeFile.textureNameLeaves = new string(br.ReadChars(64)).Replace("\0", "");
            extension = Path.GetExtension(treeFile.textureNameLeaves).ToUpper();
            if (extension == ".TGA")
                treeFile.textureNameLeaves = treeFile.textureNameLeaves.Substring(0, treeFile.textureNameLeaves.Length - 3) + "ptx";

            br.BaseStream.Position += 108; // Don´t know what this is atm

            // Loads all vertices of the tree.
            treeFile.vertices = new MyOwnVertex.MyPositionNormalTextured[treeFile.header.numberOfVertices];
            for (int i = 0; i < treeFile.vertices.Length; i++)
            {
                Vector3 position = new Vector3()
                {
                    X = br.ReadSingle(),
                    Y = br.ReadSingle(),
                    Z = br.ReadSingle()
                };
                treeFile.vertices[i] = new MyOwnVertex.MyPositionNormalTextured();
                treeFile.vertices[i].Position = position * 2.0f;
                treeFile.vertices[i].Normal = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                // br.BaseStream.Position += 12; // Direction or scale???
                treeFile.vertices[i].Tu = br.ReadSingle();
                treeFile.vertices[i].Tv = br.ReadSingle();
            }

            // Load the leave positons.
            treeFile.leavePositions = new LeavePosition[treeFile.header.numberOfLeaves];
            for (int i = 0; i < treeFile.leavePositions.Length; i++)
            {
                /// 4 vertices are stored for each leave.
                /// I load only two diagonal ones to get the middle of the leave.
                LeavePosition position = new LeavePosition()
                {
                    Position = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), 1.0f),
                    Color = br.ReadInt32()
                };
                //br.BaseStream.Position += 16;

                //LeavePosition position2 = new LeavePosition()
                // {
                //     Position = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), 1.0f),
                //     Color = br.ReadInt32()
                // };
                ////treeFile.leavePositions[i].Position *= 2.0f;
                //br.BaseStream.Position += 16;

                treeFile.leavePositions[i].Position = position.Position * 2.0f;// Vector4.Add(position.Position, position2.Position);
            }

            // Load indices for the polygons.
            treeFile.indices = new short[treeFile.header.numberOfIndices];
            for (int i = 0; i < treeFile.indices.Length; i++)
            {
                treeFile.indices[i] = br.ReadInt16();
            }

            // Update the vertexbuffer.
            treeFile.UpdateVertexBuffer();
            return treeFile;
        }

        private void UpdateVertexBuffer()
        {
            // Delete old buffers.
            Dispose();

            MyOwnVertex.MyPositionNormalTextured[] transformedVertices = new MyOwnVertex.MyPositionNormalTextured[this.vertices.Length];
            this.vertices.CopyTo(transformedVertices, 0);

            this.vertexBufferDescription.SizeInBytes = this.vertices.Length * MyOwnVertex.MyPositionNormalTextured.SizeInBytes();
            this.vb = SharpDX.Direct3D11.Buffer.Create(this.engine.Device, transformedVertices, this.vertexBufferDescription);
            this.vertexBufferBinding = new VertexBufferBinding(this.vb, MyOwnVertex.MyPositionNormalTextured.SizeInBytes(), 0);

            this.indexBufferDescription.SizeInBytes = 2 * this.indices.Length;
            this.ib = SharpDX.Direct3D11.Buffer.Create(this.engine.Device, this.indices, this.indexBufferDescription);

            this.instanceLeavesBufferDescription.SizeInBytes = this.leavePositions.Length * Marshal.SizeOf(typeof(LeavePosition));
            this.instanceLeavesBuffer = SharpDX.Direct3D11.Buffer.Create(this.engine.Device, this.leavePositions, this.instanceLeavesBufferDescription);
            this.vertexBufferBindingLeaves = new VertexBufferBinding(this.InstanceLeavesBuffer, Marshal.SizeOf(typeof(TreeFile.LeavePosition)), 0);
        }

        #endregion Private Methods

        #region Public Structs

        public struct Header
        {
            #region Public Fields

            public int numberOfIndices;
            public int numberOfLeaves;
            public int numberOfVertices;

            #endregion Public Fields

            #region Private Fields

            private static char[] FILETYPE = new char[] { 'E', 'E', 'R', 'T' };

            #endregion Private Fields

            #region Public Methods

            public static Header FromStream(BinaryReader br)
            {
                Header header = new Header();

                char[] tes = br.ReadChars(4);
                if (tes.ToString() != FILETYPE.ToString())
                    throw new Exception("Wrong File Format!");

                header.numberOfVertices = br.ReadInt32();
                header.numberOfLeaves = (br.ReadInt32());
                header.numberOfIndices = br.ReadInt32();

                return header;
            }

            #endregion Public Methods
        }

        [StructLayout(LayoutKind.Explicit, Size = 32)]
        public struct LeavePosition
        {
            [FieldOffset(0)]
            public Vector4 Position;

            [FieldOffset(16)]
            public int Color;

            public override string ToString()
            {
                return this.Position.ToString();
            }
        }

        #endregion Public Structs
    }
}